12. 理解监督微调
这一章真正需要理解的只有一句话:
SFT 不是教模型学知识,而是教模型如何像一个助手一样回答问题。
很多人第一次学 SFT,会觉得它和预训练差不多,都是预测下一个 Token。实际上,两者最大的区别不是模型,也不是 Loss,而是"老师到底在教什么"。
一张图看懂整个 LLM 训练流程

一句话:
Pretrain = 学知识
SFT = 学礼貌、学聊天、学回答。
一、为什么需要 SFT?
举一个最简单的例子。
假设一个人天天看百科全书。
天天看:
太阳系有八大行星。
木星是最大的行星。
深度学习属于机器学习。
北京是中国首都。
看了十年。
最后问他:
北京在哪?
他可能回答:
北京在哪?
北京是中华人民共和国首都,
位于华北平原……
甚至继续写一整篇百科。
为什么?
因为他一辈子看的都是
一句接一句的文章。
根本没人教过他:
别人提问题,你应该回答。
这就是预训练模型。
预训练模型其实不会聊天
它真正学的是:
前面一句
↓
预测下一句
例如:
天空很蓝,
↓
预测:
今天阳光很好。
再比如
深度学习属于
↓
预测:
机器学习。
它一直都是:
接着写。
不是:
回答问题。
所以你输入
请介绍一下北京
模型心里想的是:
哦,这句话后面应该继续写。
请介绍一下北京,它位于......
而不是
用户问我问题,
我要回答。
所以:
Pretrain 学的是写文章。
不是聊天。
二、SFT 到底教了什么?
SFT 做了一件事情:
告诉模型:"以后看到 User,就回答 Assistant。"
例如:
训练数据变成
User:
北京在哪?
Assistant:
北京位于中国。
模型看到无数这样的例子。
几十万条。
几百万条。
几千万条。
最后模型发现:
原来
User:
xxxx
Assistant:
后面应该生成:
真正的回答。
于是它终于学会:
别人问问题,我回答。
用老师批改作业理解
预训练像这样:
老师说:
今天
↓
继续写。
学生写:
天气很好。
老师打分。
一直如此。
SFT 就变成:
老师说:
用户:
北京在哪?
助手:
学生写:
北京位于中国。
老师打分。
于是模型终于知道:
只有 Assistant 的内容才是我要写的。
三、为什么要 Chat Template?
这是很多初学者最容易忽略的地方。
模型其实根本不知道:
User:
Assistant:
什么意思。
它看到的永远只是:
Token Token Token Token...
所以必须告诉它:
谁在说话。
于是出现 Chat Template。
例如:
<|im_start|>user
北京在哪?
<|im_end|>
<|im_start|>assistant
北京位于中国。
<|im_end|>
对于人来说,这是聊天。
对于模型来说,就是一种固定格式。
像 HTML 一样。
可以画成这样:
聊天记录
User:
北京在哪?
Assistant:
北京位于中国。
变成
<|im_start|>
user
北京在哪?
<|im_end|>
<|im_start|>
assistant
北京位于中国。
<|im_end|>
模型并不知道什么叫 User。
它只知道:
Token 1524
Token 378
Token 980
......
但是由于训练了几百万次。
它渐渐发现:
每次
<|im_start|>assistant
后面都会出现:
回答。
于是形成了条件反射。
四、为什么只计算 Assistant 的 Loss?
这是 SFT 最核心 的知识点。
很多同学第一次都会问:
User 为什么不训练?
答案其实非常简单。
假设:
User:
北京在哪?
Assistant:
北京位于中国。
真正需要模型学习的是哪一句?
当然是:
北京位于中国。
因为:
User 不是模型写的。
而是人输入的。
模型根本不用学。
可以画出来。
User:
北京在哪?
Assistant:
北京位于中国。
Loss:
北京 在 哪?
✗ ✗ ✗
北京 位于 中国。
✓ ✓ ✓ ✓
为什么?
因为:
模型以后运行时,
User 永远都是人输入。
模型负责生成的是:
Assistant。
再举一个更生活化的例子
像考试。
老师发卷子:
1+1=?
学生写:
2
老师评分。
老师会不会给
1+1=?
打分?
不会。
因为:
题目不是学生写的。
学生只负责:
答案。
SFT 完全一样。
User
↓
题目
Assistant
↓
答案
Loss 只看答案。
五、Loss Mask 到底是什么?
其实就是:
哪些 Token 参加考试。
例如:
User:
北京在哪?
Assistant:
北京在中国。
Mask:
User:
0 0 0 0 0
Assistant:
1 1 1 1 1
可以理解成:
老师拿了一支荧光笔。
把
User
全部划掉。
只保留:
Assistant
最后真正计算 Loss 的只有:
北京 在 中国 。
可以画成下面这样
Token
User 北京 在 哪?
Mask
0 0 0 0
Assistant 北京 位于 中国 。
Mask
1 1 1 1 1
Loss:
0×loss
+
1×loss
所以:
User 不产生梯度
Assistant 产生梯度
六、为什么还要预测 <|im_end|>?
很多人第一次都会疑惑:
为什么结束符也要算 Loss?
原因很简单。
否则模型不知道:
什么时候结束。
例如:
User:
北京在哪?
如果没训练
<|im_end|>
模型可能一直说:
北京位于中国。
中国有34个省。
北京有......
......
......
停不下来。
所以要训练:
北京 位于 中国 。
<|im_end|>
模型学会:
回答完了,就结束。
七、Pretrain 和 SFT 的本质区别
可以用一个老师的比喻。
第一阶段(Pretrain)
老师:
把这本百科全书背下来。
学生学到:
大量知识。
第二阶段(SFT)
老师:
以后别人问问题,
不要背百科,
直接回答。
学生学到:
如何交流。
所以:
Pretrain
知识增加 ↑↑↑
聊天能力 ↓
而:
SFT
知识增加 很少
聊天能力 ↑↑↑↑↑
八、一张图总结 SFT
flowchart TD A["海量文本
Pretrain"] --> B["模型学会知识
语言、数学、代码"] B --> C["SFT 对话数据"] C --> D["Chat Template"] D --> E["Tokenizer"] E --> F["Token序列"] F --> G["Loss Mask"] G --> H["User/System = 0
Assistant = 1"] H --> I["CrossEntropy"] I --> J["只更新回答部分梯度"] J --> K["模型学会如何回答问题"]
九、一句话总结
可以把整个 SFT 浓缩成四句话:
Pretrain 教模型会写,SFT 教模型会答。
Chat Template 负责告诉模型谁在说话。
Loss Mask 负责告诉模型哪些 Token 需要学习。
SFT 本质仍然是 Next Token Prediction,只不过只对 Assistant 回复部分计算 Loss。